/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
module dwt.widgets.tablecolumn;


private import dwt.dwt;

public import dwt.widgets.item;
private import dwt.widgets.table;
private import dwt.widgets.typedlistener;
private import dwt.internal.win32.os;
private import dwt.graphics.image;
private import dwt.graphics.rectangle;
private import dwt.events.events;
private import dwt.events.listeners;
private import dwt.util.eventhandler;

/**
 * Instances of this class represent a column in a table widget.
 *  <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>LEFT, RIGHT, CENTER</dd>
 * <dt><b>Events:</b></dt>
 * <dd> Move, Resize, Selection</dd>
 * </dl>
 * <p>
 * Note: Only one of the styles LEFT, RIGHT and CENTER may be specified.
 * </p><p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 */
public class TableColumn : Item {
	Table parent;
	boolean resizable;

/**
 * Constructs a new instance of this class given its parent
 * (which must be a <code>Table</code>) and a style value
 * describing its behavior and appearance. The item is added
 * to the end of the items maintained by its parent.
 * <p>
 * The style value is either one of the style constants defined in
 * class <code>SWT</code> which is applicable to instances of this
 * class, or must be built by <em>bitwise OR</em>'ing together 
 * (that is, using the <code>int</code> "|" operator) two or more
 * of those <code>SWT</code> style constants. The class description
 * lists the style constants that are applicable to the class.
 * Style bits are also inherited from superclasses.
 * </p>
 *
 * @param parent a composite control which will be the parent of the new instance (cannot be null)
 * @param style the style of control to construct
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
 * </ul>
 *
 * @see SWT#LEFT
 * @see SWT#RIGHT
 * @see SWT#CENTER
 * @see Widget#checkSubclass
 * @see Widget#getStyle
 */
public this (Table parent, int style) {
	super (parent, checkStyle (style));
	resizable = true;
	this.parent = parent;
	parent.createItem (this, parent.getColumnCount ());
}

/**
 * Constructs a new instance of this class given its parent
 * (which must be a <code>Table</code>), a style value
 * describing its behavior and appearance, and the index
 * at which to place it in the items maintained by its parent.
 * <p>
 * The style value is either one of the style constants defined in
 * class <code>SWT</code> which is applicable to instances of this
 * class, or must be built by <em>bitwise OR</em>'ing together 
 * (that is, using the <code>int</code> "|" operator) two or more
 * of those <code>SWT</code> style constants. The class description
 * lists the style constants that are applicable to the class.
 * Style bits are also inherited from superclasses.
 * </p>
 *
 * @param parent a composite control which will be the parent of the new instance (cannot be null)
 * @param style the style of control to construct
 * @param index the index to store the receiver in its parent
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
 * </ul>
 *
 * @see SWT#LEFT
 * @see SWT#RIGHT
 * @see SWT#CENTER
 * @see Widget#checkSubclass
 * @see Widget#getStyle
 */
public this (Table parent, int style, int index) {
	super (parent, checkStyle (style));
	resizable = true;
	this.parent = parent;
	parent.createItem (this, index);
}

/**
 * Adds the listener to the collection of listeners who will
 * be notified when the control is moved or resized, by sending
 * it one of the messages defined in the <code>ControlListener</code>
 * interface.
 *
 * @param listener the listener which should be notified
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @see ControlListener
 * @see #removeControlListener
 */
public void addControlListener(ControlListener listener) {
	checkWidget ();
	if (listener is null) error(__FILE__, __LINE__, DWT.ERROR_NULL_ARGUMENT);
	TypedListener typedListener = new TypedListener (listener);
	addListener (DWT.Resize,typedListener);
	addListener (DWT.Move,typedListener);
}

/**
 * Adds the listener to the collection of listeners who will
 * be notified when the control is selected, by sending
 * it one of the messages defined in the <code>SelectionListener</code>
 * interface.
 * <p>
 * <code>widgetSelected</code> is called when the column header is selected.
 * <code>widgetDefaultSelected</code> is not called.
 * </p>
 *
 * @param listener the listener which should be notified
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @see SelectionListener
 * @see #removeSelectionListener
 * @see SelectionEvent
 */
public void addSelectionListener (SelectionListener listener) {
	checkWidget ();
	if (listener is null) error(__FILE__, __LINE__, DWT.ERROR_NULL_ARGUMENT);
	TypedListener typedListener = new TypedListener (listener);
	addListener (DWT.Selection,typedListener);
	addListener (DWT.DefaultSelection,typedListener);
}

static int checkStyle (int style) {
	return checkBits (style, DWT.LEFT, DWT.CENTER, DWT.RIGHT, 0, 0, 0);
}

protected void checkSubclass () {
	if (!isValidSubclass ()) error(__FILE__, __LINE__, DWT.ERROR_INVALID_SUBCLASS);
}

/**
 * Returns a value which describes the position of the
 * text or image in the receiver. The value will be one of
 * <code>LEFT</code>, <code>RIGHT</code> or <code>CENTER</code>.
 *
 * @return the alignment 
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getAlignment () {
	checkWidget ();
	if ((style & DWT.LEFT) != 0) return DWT.LEFT;
	if ((style & DWT.CENTER) != 0) return DWT.CENTER;
	if ((style & DWT.RIGHT) != 0) return DWT.RIGHT;
	return DWT.LEFT;
}

char[] getNameText () {
	return getText ();
}

/**
 * Returns the receiver's parent, which must be a <code>Table</code>.
 *
 * @return the receiver's parent
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public Table getParent () {
	checkWidget ();
	return parent;
}

/**
 * Gets the resizable attribute. A column that is
 * not resizable cannot be dragged by the user but
 * may be resized by the programmer.
 *
 * @return the resizable attribute
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public boolean getResizable () {
	checkWidget ();
	return resizable;
}

/**
 * Gets the width of the receiver.
 *
 * @return the width
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getWidth () {
	checkWidget ();
	int index = parent.indexOf (this);
	if (index == -1) return 0;
	HWND hwnd = parent.handle;
	return OS.SendMessage (hwnd, OS.LVM_GETCOLUMNWIDTH, index, 0);
}

/**
 * Causes the receiver to be resized to its preferred size.
 * For a composite, this involves computing the preferred size
 * from its layout, if there is one.
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 */
public void pack () {
	checkWidget ();
	int index = parent.indexOf (this);
	if (index == -1) return;
	HWND hwnd = parent.handle;
	int headerWidth = OS.SendMessage (hwnd, OS.LVM_GETSTRINGWIDTH, 0, Converter.StrToTCHARz(text)) + 10;
	if (image !is null || parent.sortColumn is this) {
		Image headerImage;
		if(parent.sortColumn is this && parent.sortDirection != DWT.NULL) {
			if (OS.COMCTL32_MAJOR < 6) {
				headerImage = display.getSortImage (parent.sortDirection);
			} else {
				headerWidth += 10; //Table.SORT_WIDTH;
			}
		}else{
			headerImage = image;
		}
		if (headerImage !is null) {
			Rectangle bounds = headerImage.getBounds ();
			headerWidth += bounds.width;
		}
		int margin = 0;
		if ((OS.COMCTL32_MAJOR << 16 | OS.COMCTL32_MINOR) >= (5 << 16 | 80)) {
			HWND hwndHeader = cast(HWND)OS.SendMessage (hwnd, OS.LVM_GETHEADER, 0, 0);
			margin = OS.SendMessage (hwndHeader, OS.HDM_GETBITMAPMARGIN, 0, 0);
		} else {
			margin = OS.GetSystemMetrics (OS.SM_CXEDGE) * 3;
		}
		headerWidth += margin * 4;
	}
	if ((parent.style & DWT.VIRTUAL) != 0) {
		if (image is null && parent.sortColumn !is this) {
			OS.SendMessage (hwnd, OS.LVM_SETCOLUMNWIDTH, index, OS.LVSCW_AUTOSIZE_USEHEADER);
		} else {
			OS.SendMessage (hwnd, OS.LVM_SETCOLUMNWIDTH, index, headerWidth);
		}		
	} else {
		OS.SendMessage (hwnd, OS.LVM_SETCOLUMNWIDTH, index, OS.LVSCW_AUTOSIZE);
		int columnWidth = OS.SendMessage (hwnd, OS.LVM_GETCOLUMNWIDTH, index, 0);
		/*
		* Bug in Windows.  When LVM_SETCOLUMNWIDTH is used with LVSCW_AUTOSIZE
		* where each item has I_IMAGECALLBACK but there are no images in the
		* table, the size computed by LVM_SETCOLUMNWIDTH is to small for the
		* first column, causing long items to be clipped with '...'.  The fix
		* is to increase the value by a small amount. 
		*/
		if (index == 0 && parent.imageList is null) columnWidth += 2;
		if (headerWidth > columnWidth) {
			if (image is null) {
				OS.SendMessage (hwnd, OS.LVM_SETCOLUMNWIDTH, index, OS.LVSCW_AUTOSIZE_USEHEADER);
			} else {
				OS.SendMessage (hwnd, OS.LVM_SETCOLUMNWIDTH, index, headerWidth);
			}
		} else {
			if (index == 0) {
				OS.SendMessage (hwnd, OS.LVM_SETCOLUMNWIDTH, index, columnWidth);
			}
		}
	}
}

void releaseChild () {
	super.releaseChild ();
	parent.destroyItem (this);
}

void releaseWidget () {
	super.releaseWidget ();
	if (parent.sortColumn is this) {
		parent.sortColumn = null;
	}
	parent = null;
}

/**
 * Removes the listener from the collection of listeners who will
 * be notified when the control is moved or resized.
 *
 * @param listener the listener which should be notified
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @see ControlListener
 * @see #addControlListener
 */
public void removeControlListener (ControlListener listener) {
	checkWidget ();
	if (listener is null) error(__FILE__, __LINE__, DWT.ERROR_NULL_ARGUMENT);
	if (eventTable is null) return;
	eventTable.unhook (DWT.Move, listener);
	eventTable.unhook (DWT.Resize, listener);
}

/**
 * Removes the listener from the collection of listeners who will
 * be notified when the control is selected.
 *
 * @param listener the listener which should be notified
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @see SelectionListener
 * @see #addSelectionListener
 */
public void removeSelectionListener(SelectionListener listener) {
	checkWidget ();
	if (listener is null) error(__FILE__, __LINE__, DWT.ERROR_NULL_ARGUMENT);
	if (eventTable is null) return;
	eventTable.unhook (DWT.Selection, listener);
	eventTable.unhook (DWT.DefaultSelection,listener);	
}

/**
 * Controls how text and images will be displayed in the receiver.
 * The argument should be one of <code>LEFT</code>, <code>RIGHT</code>
 * or <code>CENTER</code>.
 *
 * @param alignment the new alignment 
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setAlignment (int alignment) {
	checkWidget ();
	if ((alignment & (DWT.LEFT | DWT.RIGHT | DWT.CENTER)) == 0) return;
	int index = parent.indexOf (this);
	if (index == -1 || index == 0) return;
	style &= ~(DWT.LEFT | DWT.RIGHT | DWT.CENTER);
	style |= alignment & (DWT.LEFT | DWT.RIGHT | DWT.CENTER);
	HWND hwnd = parent.handle;
	LVCOLUMN* lvColumn = new LVCOLUMN ();
	lvColumn.mask = OS.LVCF_FMT | OS.LVCF_IMAGE;
	OS.SendMessage (hwnd, OS.LVM_GETCOLUMN, index, lvColumn);
	lvColumn.fmt &= ~OS.LVCFMT_JUSTIFYMASK;
	int fmt = 0;
	if ((style & DWT.LEFT) == DWT.LEFT) fmt = OS.LVCFMT_LEFT;
	if ((style & DWT.CENTER) == DWT.CENTER) fmt = OS.LVCFMT_CENTER;
	if ((style & DWT.RIGHT) == DWT.RIGHT) fmt = OS.LVCFMT_RIGHT;
	lvColumn.fmt |= fmt;
	OS.SendMessage (hwnd, OS.LVM_SETCOLUMN, index, lvColumn);
}

/*
public void setImage (Image image) {
	checkWidget();
	if (image !is null && image.isDisposed ()) {
		error(__FILE__, __LINE__, SWT.ERROR_INVALID_ARGUMENT);
	}
	int index = parent.indexOf (this);
	if (index == -1) return;
	super.setImage (image);
	HWND hwnd = parent.handle;
	LVCOLUMN* lvColumn = new LVCOLUMN ();
	lvColumn.mask = OS.LVCF_FMT | OS.LVCF_IMAGE;
	OS.SendMessage (hwnd, OS.LVM_GETCOLUMN, index, lvColumn);
	if (image !is null) {
		lvColumn.fmt |= OS.LVCFMT_IMAGE;
		lvColumn.iImage = parent.imageIndex (image);
	} else {
		lvColumn.fmt &= ~OS.LVCFMT_IMAGE;
	}
	OS.SendMessage (hwnd, OS.LVM_SETCOLUMN, index, lvColumn);
}
*/

public void setImage (Image image) {
	checkWidget();
	if (image !is null && image.isDisposed ()) {
		error (__FILE__, __LINE__, DWT.ERROR_INVALID_ARGUMENT);
	}
	if(parent.indexOf(this)==-1) return;
	super.setImage (image);
	if (parent.sortColumn !is this || parent.sortDirection != DWT.NULL) {
		setImage (image, false, false);
	}
}

void setImage (Image image, boolean sort, boolean right) {
	int index = parent.indexOf (this);
	if (index == -1) return;
	HWND hwnd = parent.handle;
	/*
	if (OS.COMCTL32_MAJOR < 6) {
		HWND hwndHeader = cast(HWND)OS.SendMessage (hwnd, OS.LVM_GETHEADER, 0, 0);
		HDITEM hdItem = new HDITEM ();
		hdItem.mask = OS.HDI_FORMAT | OS.HDI_IMAGE | OS.HDI_BITMAP;
		OS.SendMessage (hwndHeader, OS.HDM_GETITEM, index, hdItem);
		hdItem.fmt &= ~OS.HDF_BITMAP_ON_RIGHT;
		if (image !is null) {
			if (sort) {
				hdItem.mask &= ~OS.HDI_IMAGE;
				hdItem.fmt &= ~OS.HDF_IMAGE;
				hdItem.fmt |= OS.HDF_BITMAP;
				hdItem.hbm = image.handle;
			} else {
				hdItem.mask &= ~OS.HDI_BITMAP;
				hdItem.fmt &= ~OS.HDF_BITMAP;
				hdItem.fmt |= OS.HDF_IMAGE;
				hdItem.iImage = parent.imageIndexHeader (image);
			}
			if (right) hdItem.fmt |= OS.HDF_BITMAP_ON_RIGHT;
		} else {
			hdItem.fmt &= ~(OS.HDF_IMAGE | OS.HDF_BITMAP);
		}
		OS.SendMessage (hwndHeader, OS.HDM_SETITEM, index, hdItem);
	} else */{
		LVCOLUMN* lvColumn = new LVCOLUMN ();
		lvColumn.mask = OS.LVCF_FMT | OS.LVCF_IMAGE;
		OS.SendMessage (hwnd, OS.LVM_GETCOLUMN, index, lvColumn);
		if (image !is null) {
			lvColumn.fmt |= OS.LVCFMT_IMAGE;
			lvColumn.iImage = parent.imageIndexHeader (image);
			if (right) lvColumn.fmt |= OS.LVCFMT_BITMAP_ON_RIGHT;
		} else {
			lvColumn.fmt &= ~(OS.LVCFMT_IMAGE | OS.LVCFMT_BITMAP_ON_RIGHT);
		}
		OS.SendMessage (hwnd, OS.LVM_SETCOLUMN, index, lvColumn);
	}
}

/**
 * Sets the resizable attribute.  A column that is
 * not resizable cannot be dragged by the user but
 * may be resized by the programmer.
 *
 * @param resizable the resize attribute
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setResizable (boolean resizable) {
	checkWidget ();
	this.resizable = resizable;
}

void setSortDirection (int direction) {
	if (OS.COMCTL32_MAJOR >= 6) {
		int index = parent.indexOf (this);
		if (index == -1) return;
		HWND hwnd = parent.handle;
		HWND hwndHeader = cast(HWND)OS.SendMessage (hwnd, OS.LVM_GETHEADER, 0, 0);
		HDITEM* hdItem = new HDITEM ();
		hdItem.mask = OS.HDI_FORMAT | OS.HDI_IMAGE;
		OS.SendMessage (hwndHeader, OS.HDM_GETITEM, index, hdItem);
		switch (direction) {
			case DWT.UP:
				hdItem.fmt &= ~(OS.HDF_IMAGE | OS.HDF_SORTDOWN);
				hdItem.fmt |= OS.HDF_SORTUP;
				break;
			case DWT.DOWN:
				hdItem.fmt &= ~(OS.HDF_IMAGE | OS.HDF_SORTUP);
				hdItem.fmt |= OS.HDF_SORTDOWN;
				break;
			case DWT.NONE:
				hdItem.fmt &= ~(OS.HDF_SORTUP | OS.HDF_SORTDOWN);
				if (image !is null) {
					hdItem.fmt |= OS.HDF_IMAGE;
					hdItem.iImage = parent.imageIndex (image);
				} else {
					hdItem.fmt &= ~OS.HDF_IMAGE;
				}
				break;
			default: break;
		}
		OS.SendMessage (hwndHeader, OS.HDM_SETITEM, index, hdItem);
	} else {
		switch (direction) {
			case DWT.UP:
			case DWT.DOWN:
				setImage (display.getSortImage (direction), true, true);
				break;
			case DWT.NONE:
				setImage (image, false, false);
				break;
			default: break;
		}
	}
}

public void setText (char[] string) {
	checkWidget ();
	if (string is null) 
		string = "";
	int index = parent.indexOf (this);
	if (index == -1) return;
	super.setText (string);

	/*
	* Bug in Windows.  For some reason, when the title
	* of a column is changed after the column has been
	* created, the alignment must also be reset or the
	* text does not draw.  The fix is to query and then
	* set the alignment.
	*/
	HWND hwnd = parent.handle;
	LVCOLUMN lvColumn;
	lvColumn.mask = OS.LVCF_FMT;
	OS.SendMessage (hwnd, OS.LVM_GETCOLUMN, index, &lvColumn);

	/* Set the column title */
	lvColumn.mask |= OS.LVCF_TEXT;
	lvColumn.pszText = Converter.StrToTCHARz(string);
	int result = OS.SendMessage (hwnd, OS.LVM_SETCOLUMN, index, &lvColumn);
	if (result == 0) error(__FILE__, __LINE__, DWT.ERROR_CANNOT_SET_TEXT);
}

/**
 * Sets the width of the receiver.
 *
 * @param width the new width
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setWidth (int width) {
	checkWidget ();
	int index = parent.indexOf (this);
	if (index == -1) return;
	HWND hwnd = parent.handle;
	OS.SendMessage (hwnd, OS.LVM_SETCOLUMNWIDTH, index, width);
}


// Event Handler
public void handleResize(Object customData, void delegate(ControlEvent)func){
	handleEvent(new ControlHandler(customData, DWT.Resize, func));
}
public void handleMove(Object customData, void delegate(ControlEvent) func){
	handleEvent(new ControlHandler(customData, DWT.Move, func));
}
public void handleSelection(Object customData, void delegate(SelectionEvent) func){
	handleEvent(new SelectionHandler(customData, DWT.Selection, func));
}
public void handleDefaultSelection(Object customData, void delegate(SelectionEvent) func) {
	handleEvent(new SelectionHandler(customData, DWT.DefaultSelection, func));
}

}
